package com.atom.plugin.core.utils

import com.atom.plugin.core.Log
import java.io.*
import java.util.*
import java.util.regex.Pattern
import java.util.zip.ZipEntry
import java.util.zip.ZipException
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream

object FileUtils {
    private const val BUFFER_SIZE = 16384

    @JvmStatic
    fun isLegalFile(file: File?): Boolean {
        return file != null && file.exists() && file.canRead() && file.isFile && file.length() > 0
    }

    @JvmStatic
    fun isLegalFile(filename: String): Boolean {
        val file = File(filename)
        return isLegalFile(file)
    }

    /**
     * get directory size
     *
     * @param directory
     * @return
     */
    @JvmStatic
    fun getFileOrDirectorySize(directory: File?): Long {
        if (directory == null || !directory.exists()) {
            return 0
        }
        if (directory.isFile) {
            return directory.length()
        }
        var totalSize: Long = 0
        val fileList = directory.listFiles()
        if (fileList != null) {
            for (file in fileList) {
                totalSize = if (file.isDirectory) {
                    totalSize + getFileOrDirectorySize(file)
                } else {
                    totalSize + file.length()
                }
            }
        }
        return totalSize
    }

    @JvmStatic
    fun safeDeleteFile(file: File?): Boolean {
        if (file == null) {
            return true
        }
        if (file.exists()) {
            val deleted = file.delete()
            if (!deleted) {
                Log.e(
                    "Failed to delete file, try to delete when exit. path: " + file.path
                )
                file.deleteOnExit()
            }
            return deleted
        }
        return true
    }

    @JvmStatic
    fun deleteDir(dir: String?): Boolean {
        return if (dir == null) {
            false
        } else deleteDir(File(dir))
    }

    @JvmStatic
    fun deleteDir(file: File?): Boolean {
        if (file == null || !file.exists()) {
            return false
        }
        if (file.isFile) {
            safeDeleteFile(file)
        } else if (file.isDirectory) {
            val files = file.listFiles()
            if (files != null) {
                for (subFile in files) {
                    deleteDir(subFile)
                }
                safeDeleteFile(file)
            }
        }
        return true
    }

    /**
     * Closes the given `Closeable`. Suppresses any IO exceptions.
     */
    @JvmStatic
    fun closeQuietly(closeable: Closeable?) {
        try {
            closeable?.close()
        } catch (e: IOException) {
            Log.w("Failed to close resource :" + e.localizedMessage)
        }
    }

    @JvmStatic
    fun closeZip(zipFile: ZipFile?) {
        try {
            zipFile?.close()
        } catch (e: IOException) {
            Log.w("Failed to close resource :" + e.localizedMessage)
        }
    }

    @JvmStatic
    fun ensureFileDirectory(file: File?) {
        if (file == null) {
            return
        }
        val parentFile = file.parentFile
        if (!parentFile.exists()) {
            parentFile.mkdirs()
        }
    }

    @JvmStatic
    @Throws(IOException::class)
    fun copyResourceUsingStream(name: String, dest: File) {
        var os: FileOutputStream? = null
        val parent = dest.parentFile
        if (parent != null && !parent.exists()) {
            parent.mkdirs()
        }
        var `is`: InputStream? = null
        try {
            `is` = FileUtils.javaClass.getResourceAsStream("/$name")
            os = FileOutputStream(dest, false)
            val buffer = ByteArray(BUFFER_SIZE)
            var length: Int
            while (`is`.read(buffer).also { length = it } > 0) {
                os.write(buffer, 0, length)
            }
        } finally {
            closeQuietly(`is`)
            closeQuietly(os)
        }
    }

    @JvmStatic
    @Throws(IOException::class)
    fun copyFileUsingStream(source: File, dest: File) {
        var `is`: FileInputStream? = null
        var os: FileOutputStream? = null
        val parent = dest.parentFile
        if (parent != null && !parent.exists()) {
            parent.mkdirs()
        }
        try {
            `is` = FileInputStream(source)
            os = FileOutputStream(dest, false)
            val buffer = ByteArray(BUFFER_SIZE)
            var length: Int
            while (`is`.read(buffer).also { length = it } > 0) {
                os.write(buffer, 0, length)
            }
        } finally {
            closeQuietly(`is`)
            closeQuietly(os)
        }
    }

    @JvmStatic
    fun checkDirectory(dir: String): Boolean {
        val dirObj = File(dir)
        deleteDir(dirObj)
        if (!dirObj.exists()) {
            dirObj.mkdirs()
        }
        return true
    }

    @JvmStatic
    fun readFileAsString(filePath: String): String {
        if (!File(filePath).exists()) {
            return ""
        }
        val fileData = StringBuffer()
        var fileReader: Reader? = null
        var inputStream: InputStream? = null
        try {
            inputStream = FileInputStream(filePath)
            fileReader = InputStreamReader(inputStream, "UTF-8")
            val buf = CharArray(BUFFER_SIZE)
            var numRead = 0
            while (fileReader.read(buf).also { numRead = it } != -1) {
                val readData = String(buf, 0, numRead)
                fileData.append(readData)
            }
        } catch (e: Exception) {
            Log.e(
                String.format(
                    "file op readFileAsString e type:%s, e msg:%s, filePath:%s",
                    e.javaClass.simpleName,
                    e.message,
                    filePath
                )
            )
            return ""
        } finally {
            try {
                closeQuietly(fileReader)
                closeQuietly(inputStream)
            } catch (e: Exception) {
                Log.e(
                    String.format(
                        "file op readFileAsString close e type:%s, e msg:%s, filePath:%s",
                        e.javaClass.simpleName,
                        e.message,
                        filePath
                    )

                )
            }
        }
        return fileData.toString()
    }

    @JvmStatic
    fun unzip(filePath: String, destFolder: String) {
        var zipFile: ZipFile? = null
        var bos: BufferedOutputStream? = null
        var bis: BufferedInputStream? = null
        try {
            zipFile = ZipFile(filePath)
            val emu: Enumeration<*> = zipFile.entries()
            while (emu.hasMoreElements()) {
                val entry = emu.nextElement() as ZipEntry
                val entryName = entry.name
                if (ZipUtils.preventZipSlip(File(destFolder), entryName)) {
                    Log.e(String.format("writeEntry entry %s failed!", entryName))
                    continue
                }
                if (entry.isDirectory) {
                    File(destFolder, entry.name).mkdirs()
                    continue
                }
                bis = BufferedInputStream(zipFile.getInputStream(entry))
                val file = File(destFolder, entry.name)
                val parent = file.parentFile
                if (parent != null && !parent.exists()) {
                    parent.mkdirs()
                }
                val data = ByteArray(BUFFER_SIZE)
                bos = BufferedOutputStream(FileOutputStream(file), data.size)
                var count: Int
                while (bis.read(data, 0, data.size).also { count = it } != -1) {
                    bos.write(data, 0, count)
                }
                bos.flush()
                closeQuietly(bos)
            }
        } catch (e: Exception) {
            // ignore
        } finally {
            closeQuietly(zipFile)
            closeQuietly(bis)
            closeQuietly(bos)
        }
    }

    @JvmStatic
    fun zip(srcFolder: String, destZip: String) {
        var fos: FileOutputStream? = null
        var zos: ZipOutputStream? = null
        try {
            val dir = File(srcFolder)
            val filesListInDir: MutableList<String> = ArrayList()
            populateFilesList(filesListInDir, dir)
            //now zip files one by one
            //create ZipOutputStream to write to the zip file
            fos = FileOutputStream(destZip)
            zos = ZipOutputStream(fos)
            for (filePath in filesListInDir) {
                //for ZipEntry we need to keep only relative file path, so we used substring on absolute path
                val ze = ZipEntry(filePath.substring(dir.absolutePath.length + 1, filePath.length))
                zos.putNextEntry(ze)
                //read the file and write to ZipOutputStream
                val fis = FileInputStream(filePath)
                val buffer = ByteArray(BUFFER_SIZE)
                var len: Int
                while (fis.read(buffer).also { len = it } > 0) {
                    zos.write(buffer, 0, len)
                }
                zos.closeEntry()
                fis.close()
            }
            zos.close()
            fos.close()
        } catch (e: IOException) {
            e.printStackTrace()
        } finally {
            closeQuietly(zos)
            closeQuietly(fos)
        }
    }

    /**
     * This method populates all the files in a directory to a List
     *
     * @param dir
     * @throws IOException
     */
    @JvmStatic
    @Throws(IOException::class)
    private fun populateFilesList(filesListInDir: MutableList<String>, dir: File) {
        val files = dir.listFiles() ?: return
        for (file in files) {
            if (file.isFile) {
                filesListInDir.add(file.absolutePath)
            } else {
                populateFilesList(filesListInDir, file)
            }
        }
    }

    @JvmStatic
    @Throws(Exception::class)
    fun addZipEntry(
        zipOutputStream: ZipOutputStream,
        zipEntry: ZipEntry,
        inputStream: InputStream
    ) {
        try {
            zipOutputStream.putNextEntry(zipEntry)
            val buffer = ByteArray(BUFFER_SIZE)
            var length = -1
            while (inputStream.read(buffer, 0, buffer.size).also { length = it } != -1) {
                zipOutputStream.write(buffer, 0, length)
                zipOutputStream.flush()
            }
        } catch (e: ZipException) {
            Log.e("addZipEntry err!")
        } finally {
            closeQuietly(inputStream)
            zipOutputStream.closeEntry()
        }
    }

    @JvmStatic
    fun isClassFile(string: String): Boolean {
        val regex = "^[\\S|\\s]*.class$"
        val pattern = Pattern.compile(regex)
        val matcher = pattern.matcher(string)
        return matcher.find()
    }
}